package com.thoughtworks.android.androsphere.converter;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.ReflectionUtils;

public abstract class AbstractCustomRequestConverter<T> extends AbstractHttpMessageConverter<T> {
    private static final Logger logger = Logger.getLogger(AbstractCustomRequestConverter.class);

    private static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
    private ArrayList<Charset> availableCharsets;

    private Set<String> attributes;
    private static final String KEY_VALUE_SEPARATOR = ":";
    private static final String PARAM_DELIMETER = ";";

    public AbstractCustomRequestConverter(Set<String> attributes) {
        super(new MediaType("text", "plain", DEFAULT_CHARSET), MediaType.ALL);

        this.attributes = attributes;

        this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
    }

    /**
     * The correct request string should be like
     * device:a@b.com;registration:123456
     * OR
     * device:a@b.com;registration:123456;
     */
    @Override
    protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        logger.debug("Inside AbstractCustomRequestConverter - readInternal.");

        MediaType contentType = inputMessage.getHeaders().getContentType();
        Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : DEFAULT_CHARSET;
        final String requestString = FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), charset));
        final String[] requestStringParts = requestString.trim().split(PARAM_DELIMETER);

        Map<String, String> attributeValueMap = formulateAttributeMap(requestStringParts);

        if (requestStringParts.length != attributes.size() || !attributes.containsAll(attributeValueMap.keySet())) {
            logger.debug("Abstract Custom Request conversion failed.");
            throw new HttpMessageNotReadableException("Request String Not Well Formed.");
        }

        T t = createInstance(clazz);

        formulateObject(t, attributeValueMap);

        logger.debug("Abstract Custom Request conversion successful.");
        return t;
    }

    @Override
    protected void writeInternal(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        logger.debug("Inside AbstractCustomRequest - writeInternal.");

        MediaType contentType = outputMessage.getHeaders().getContentType();
        Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : DEFAULT_CHARSET;
        final OutputStreamWriter writer = new OutputStreamWriter(outputMessage.getBody(), charset);
        writer.write(getObjectAsString(t));
    }

    private Map<String, String> formulateAttributeMap(String[] requestStringParts) {
        Map<String, String> attributeValueMap = new HashMap<String, String>();

        for (String requestStringPart : requestStringParts) {
            final String[] attributeParts = requestStringPart.split(KEY_VALUE_SEPARATOR);
            attributeValueMap.put(attributeParts[0], attributeParts[1]);
        }

        return attributeValueMap;
    }

    private T createInstance(Class<? extends T> clazz) {
        T t;
        try {
            t = clazz.newInstance();
        } catch (InstantiationException e) {
            logger.debug("Abstract Custom Request conversion failed.");
            throw new HttpMessageNotReadableException("Request String Not Well Formed.");
        } catch (IllegalAccessException e) {
            logger.debug("Abstract Custom Request conversion failed.");
            throw new HttpMessageNotReadableException("Request String Not Well Formed.");
        }
        return t;
    }

    private String getObjectAsString(T t) {
        String objString = "";
        for (String attribute : attributes) {
            String getterName = "get" + attribute.substring(0, 1).toUpperCase() + attribute.substring(1);

            final Method getter = ReflectionUtils.findMethod(t.getClass(), getterName);
            final String attributeValue = (String) ReflectionUtils.invokeMethod(getter, t);

            objString += attribute + KEY_VALUE_SEPARATOR + attributeValue + PARAM_DELIMETER;
        }
        return objString;
    }

    private void formulateObject(T t, Map<String, String> attributeValueMap) {
        for (String attribute : attributeValueMap.keySet()) {
            String setterName = "set" + attribute.substring(0, 1).toUpperCase() + attribute.substring(1);

            final Method setter = ReflectionUtils.findMethod(t.getClass(), setterName, String.class);
            ReflectionUtils.invokeMethod(setter, t, attributeValueMap.get(attribute));
        }
    }
}
